package com.cd2cd.code_template.curd.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cd2cd.code_template.configuration.constants.ServiceCode;
import com.cd2cd.code_template.configuration.exceptions.ServerInternalErrorException;
import com.cd2cd.code_template.curd.config.CURDConfig;
import com.cd2cd.code_template.curd.config.CURDSetting;
import com.cd2cd.code_template.curd.mapper.BaseMapper;
import com.cd2cd.code_template.curd.service.CURDService;
import com.cd2cd.code_template.util.*;
import com.cd2cd.code_template.vo.BaseRes;
import com.cd2cd.code_template.vo.PageData;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
@Service("CURDService")
public class CURDServiceImpl implements CURDService {

    @Override
    public <T> BaseRes<PageData<T>> entityPage(String entity, Map<String, Object> params) throws Exception {

        log.info("entity={}, params={}", entity, JSONObject.toJSONString(params));

        Integer current = params.get("current") == null ? null : Integer.valueOf(params.get("current").toString());
        Integer pageSize = params.get("pageSize") == null ? null : Integer.valueOf(params.get("pageSize").toString());

        BaseRes<PageData<T>> res = new BaseRes<>(new PageData<>(current, pageSize), ServiceCode.SUCCESS);

        current = res.getData().getCurrent();
        pageSize = res.getData().getPageSize();

        Integer mysqlLength = pageSize;
        Integer mysqlOffset = (current - 1) * mysqlLength;

        Class criteriaClass = getCriteriaClass(entity);
        Object criteriaObj = criteriaClass.newInstance();

        // 设置排序

        // 设置分页
        Method setMysqlLength = criteriaClass.getMethod("setMysqlLength", Integer.class);
        Method setMysqlOffset = criteriaClass.getMethod("setMysqlOffset", Integer.class);
        Method setIsCURD = criteriaClass.getMethod("setIsCURD", String.class);

        setMysqlLength.invoke(criteriaObj, mysqlLength);
        setMysqlOffset.invoke(criteriaObj, mysqlOffset);
        setIsCURD.invoke(criteriaObj, "yes");

        // 设置查询条件查询
        setCriteriaCondition(criteriaClass, criteriaObj, entity, params);

        // 获取mapper
        Object mapperObj = AppCtxHelper.getBean(StringUtils.uncapitalize(entity + "Mapper"));
        Class mapperCla = mapperObj.getClass();

        Method countByExample = mapperCla.getMethod("countByExample", criteriaClass);
        Long total = (Long) countByExample.invoke(mapperObj, criteriaObj);
        res.getData().setTotal(total);
        if (total > 0) {
            Method selectByExample = mapperCla.getMethod("selectByExample", criteriaClass);
            List<Object> list = (List<Object>) selectByExample.invoke(mapperObj, criteriaObj);

            JSONArray notRetFieldArr = CURDConfig.getNotRetField(entity, "l");
            String[] notRetField = notRetFieldArr.toArray(new String[0]);
            Class domainClass = getClassByClaPath(CURDConfig.underlineToCamel(entity, true));
            List<T> rows = BeanUtil.voConvertListIgnore(list, domainClass, notRetField);

            res.getData().setRows(rows);
        }
        return res;
    }

    @Override
    public <T> BaseRes<T> entityInfo(String entity, Map<String, Object> params) throws Exception {
        BaseRes<T> res = new BaseRes<>(ServiceCode.SUCCESS);

        // 获取mapper
        Object mapperObj = AppCtxHelper.getBean(StringUtils.uncapitalize(entity + "Mapper"));
        Class mapperCla = mapperObj.getClass();

        // ID有可能是Integer 或是 Long 类型
        Method selectByPrimaryKey = getMethodByIntOrLong(mapperCla, "selectByPrimaryKey");
        Constructor paramM = selectByPrimaryKey.getParameterTypes()[0].getConstructor(String.class);
        Object idValue = paramM.newInstance(params.get("id").toString());

        Object entityInfo = selectByPrimaryKey.invoke(mapperObj, idValue);

        // 控制返回字段
        JSONArray notRetFieldArr = CURDConfig.getNotRetField(entity, "r");
        String[] notRetField = notRetFieldArr.toArray(new String[0]);
        Class domainClass = getClassByClaPath(CURDConfig.underlineToCamel(entity, true));
        T voConvertIgnore = (T) BeanUtil.voConvertIgnore(entityInfo, domainClass, notRetField);

        res.setData(voConvertIgnore);
        return res;
    }

    @Override
    public <T> BaseRes<T> entityUpdate(String entity, Map<String, Object> params) throws Exception {

        // copy data
        Class domainCla = getClassByClaPath(StringUtils.capitalize(entity));
        Object domainObj = getDomainObject(domainCla, params);
        log.info("domainObj={}", JSONObject.toJSONString(domainObj));

        // validate
        String c = domainCla.getPackage() + ".gen.Super" + entity + "$" + entity + "Update";
        Class gc = Class.forName(c);
        Validators.validate(domainObj, gc);

        // 获取mapper
        Object mapperObj = AppCtxHelper.getBean(StringUtils.uncapitalize(entity + "Mapper"));
        Class mapperCla = mapperObj.getClass();


        // 更新时间
        setUpdateTime(domainCla, domainObj, "update");

        Method updateByPrimaryKeySelective = mapperCla.getMethod("updateByPrimaryKeySelective", domainCla);
        Object effect = updateByPrimaryKeySelective.invoke(mapperObj, domainObj);
        log.info("effect={}", effect);

        return new BaseRes<>(ServiceCode.SUCCESS);
    }

    @Override
    public <T> BaseRes<T> entityAdd(String entity, Map<String, Object> params) throws Exception {

        // copy data
        Class domainCla = getClassByClaPath(StringUtils.capitalize(entity));
        Object domainObj = getDomainObject(domainCla, params);

        // validate
        String c = domainCla.getPackage().getName() + ".gen.Super" + entity + "$" + entity + "Insert";
        Class gc = Class.forName(c);
        Validators.validate(domainObj, gc);

        // 获取mapper
        Object mapperObj = AppCtxHelper.getBean(StringUtils.uncapitalize(entity + "Mapper"));
        Class mapperCla = mapperObj.getClass();

        // 添加时间和更新时间
        setUpdateTime(domainCla, domainObj, "create");

        log.info("domainObj={}", JSONObject.toJSONString(domainObj));

        Method insertSelective = mapperCla.getMethod("insertSelective", domainCla);
        Object effect = insertSelective.invoke(mapperObj, domainObj);
        log.info("effect={}", effect);

        return new BaseRes<>(ServiceCode.SUCCESS);
    }

    private Class getClassByClaPath(String claName) {
        String classPath = null;
        for (String pkg : CURDSetting.type_aliases_package) {
            try {
                classPath = String.format("%s.%s", pkg, claName);
                log.info("classPath={}", classPath);
                return Class.forName(classPath);
            } catch (ClassNotFoundException e) {
                log.error("classPath={}, err={}", classPath, e.getMessage());
            }
        }
        return null;
    }

    private Class getCriteriaClass(String entity) {
        String claName = String.format("gen.%sCriteria", CURDConfig.underlineToCamel(entity, true));
        return getClassByClaPath(claName);
    }

    private Method getMethodByIntOrLong(Class c, String m) {
        Method mm = null;
        try {
            mm = c.getMethod(m, Integer.class);
        } catch (NoSuchMethodException e) {
            try {
                mm = c.getMethod(m, Long.class);
            } catch (NoSuchMethodException ex) {
                log.error("error method not found cName={}, method={}", c.getName(), m);
            }
        }
        return mm;
    }

    /**
     * 字段验证 通过验证 true 不通过 false
     * 传进参数没有设置规则 则默认为通过
     * 检测 参数规则 不为空的参数
     */

    private BaseRes<Object> fieldVerify(String entity, Map<String, Object> params, String key) {

        BaseRes<Object> failedRes = new BaseRes<>(ServiceCode.INVALID_PARAMS);

        Set<String> parKeys = params.keySet();
        if (parKeys.size() == 0) {
            failedRes.setMsg("请添加参数！");
            return failedRes;
        }

        JSONObject ruleData = CURDConfig.getRuleDate(entity, key);

        Set<String> ruleKeys = ruleData.keySet();


        out:
        for (String ruleKey : ruleKeys) {
            String newRuleKey = CURDConfig.underlineToCamel(ruleKey, false);
            JSONArray ruleArr = ruleData.getJSONArray(ruleKey);

            for (Object item : ruleArr) {

                String rule = item.toString();
                String val = params.get(newRuleKey) == null ? null : params.get(newRuleKey).toString();

                if (rule.contains(CURDConfig.ruleVerify.NotNull.name())) {
                    if (!parKeys.contains(newRuleKey) || StringUtils.isBlank(val)) {
                        failedRes.setMsg(newRuleKey + "｜参数不可为空！");
                        return failedRes;
                    }
                }

                // 如果设置规则的参数  没有传 则跳过此次循环
                if (!parKeys.contains(newRuleKey)) {
                    continue out;
                }

                if (rule.contains(CURDConfig.ruleVerify.Mobile.name())) {
                    if (!RuleVerify.isMobile(val)) {
                        failedRes.setMsg(newRuleKey + "｜手机号格式不正确！");
                        return failedRes;
                    }
                } else if (rule.contains(CURDConfig.ruleVerify.Email.name())) {
                    if (!RuleVerify.isEmail(val)) {
                        failedRes.setMsg(newRuleKey + "｜Email格式不正确！");
                        return failedRes;
                    }
                } else if (rule.contains(CURDConfig.ruleVerify.IdCard.name())) {
                    if (!RuleVerify.isIdCard(val)) {
                        failedRes.setMsg(newRuleKey + "｜身份证号码格式不正确！");
                        return failedRes;
                    }
                } else if (rule.contains(CURDConfig.ruleVerify.Max.name())) {
                    JSONArray ruleParams = CURDConfig.getRuleParams(rule);
                    Integer v = ruleParams.getInteger(0);

                    if (v != null && Integer.parseInt(val) > v) {
                        failedRes.setMsg(newRuleKey + "｜已超出设定的最大值！");
                        return failedRes;
                    }


                } else if (rule.contains(CURDConfig.ruleVerify.Min.name())) {
                    JSONArray ruleParams = CURDConfig.getRuleParams(rule);
                    Integer v = ruleParams.getInteger(0);
                    if (v != null && Integer.parseInt(val) < v) {
                        failedRes.setMsg(newRuleKey + "｜未达到设定的最小值！");
                        return failedRes;
                    }

                } else if (rule.contains(CURDConfig.ruleVerify.Range.name())) {
                    JSONArray ruleParams = CURDConfig.getRuleParams(rule);
                    Integer startP = ruleParams.getInteger(0);
                    Integer endP = ruleParams.getInteger(0);
                    if (startP != null && endP != null && (Integer.parseInt(val) < startP || Integer.parseInt(val) > startP)) {
                        failedRes.setMsg(newRuleKey + "｜超出设定的区间！");
                        return failedRes;
                    }
                }
            }
        }

        return new BaseRes<>(ServiceCode.SUCCESS);
    }

    private Object getDomainObject(Class domainCla, Map<String, Object> params) {
        Object domainObj = JSONObject.parseObject(JSONObject.toJSONString(params), domainCla);
        return domainObj;
    }

    private void setUpdateTime(Class domainCla, Object domainObj, String type) {
        if ("create".equals(type)) {
            try {
                Method setCreateTime = domainCla.getMethod("setCreateTime", Date.class);
                setCreateTime.invoke(domainObj, new Date());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        try {
            Method setUpdateTime = domainCla.getMethod("setUpdateTime", Date.class);
            setUpdateTime.invoke(domainObj, new Date());
        } catch (Exception e) {
            log.error("err={}", e.getMessage(), e);
        }
    }

    @Override
    public <T> BaseRes<T> entityDel(String entity, Map<String, Object> params) {
        /**
         * delete by Id or update del_flag to 1
         */

        BaseRes<T> res = new BaseRes<>(ServiceCode.SUCCESS);
        try {
            // 获取mapper
            Object mapperObj = AppCtxHelper.getBean(StringUtils.uncapitalize(entity + "Mapper"));
            Class mapperCla = mapperObj.getClass();

            // copy data
            Class domainCla = getClassByClaPath(StringUtils.capitalize(entity));
            Object domainObj = getDomainObject(domainCla, params);

            boolean hasDelFlag = hasDelFlag(domainCla);

            Object effect = null;
            if (hasDelFlag) {
                // update
                setUpdateTime(domainCla, domainObj, "update");

                // del
                Method setDelFlag = domainCla.getMethod("setDelFlag", String.class);
                setDelFlag.invoke(domainObj, "del");

                Method updateByPrimaryKeySelective = mapperCla.getMethod("updateByPrimaryKeySelective", domainCla);
                effect = updateByPrimaryKeySelective.invoke(mapperObj, domainObj);

            } else {

                // ID有可能是Integer 或是 Long 类型
                Method deleteByPrimaryKey = getMethodByIntOrLong(mapperCla, "deleteByPrimaryKey");
                Constructor paramM = deleteByPrimaryKey.getParameterTypes()[0].getConstructor(String.class);
                Object idValue = paramM.newInstance(params.get("id").toString());

                effect = deleteByPrimaryKey.invoke(mapperObj, idValue);

            }

            log.info("effect={}", effect);

        } catch (InvocationTargetException e) {
            log.error("{}", e.getTargetException().getMessage(), e.getTargetException());
            throw new ServerInternalErrorException(ServiceCode.FAILED, e);
        } catch (Exception e) {
            throw new ServerInternalErrorException(ServiceCode.FAILED, e);
        }
        return res;
    }

    @Override
    public BaseRes<Object> dynamicDic(String entity, Map<String, Object> params) {

        BaseRes<Object> res = new BaseRes<>(ServiceCode.SUCCESS);

        try {
            /**
             * JSON样例：
             * {
             * 	"relationTable": "sys_intl_country",
             * 	"relationColumn": "id",
             * 	"relationArr": [{
             * 		"relationValue": "language",
             * 		"alias": "name"
             *        }]
             * }
             */
            String foreignKey = (String) params.get("foreignKey"); // 传入外键字段名 字段为

            // cache sql
            JSONObject reJsonObj = CURDConfig.getData(entity, "re");

            JSONObject relationObj = reJsonObj.getJSONObject(foreignKey);

            String tabName = relationObj.getString("relationTable");
            String idName = relationObj.getString("relationColumn");

            JSONArray reArr = reJsonObj.getJSONArray("relationArr");
            JSONObject reObj = reArr.getJSONObject(0);
            String reCname = reObj.getString("relationValue");
            String reCAlias = reObj.getString("alias");

            String where = ""; // where del_flag
            String sql = String.format("select %s, %s as %s from %s %s", idName, reCname, reCAlias, tabName, where);

            log.info("dynamic dic sql={}", sql);

            BaseMapper mapperObj = AppCtxHelper.getBean(StringUtils.uncapitalize(entity + "Mapper"));

            List<HashMap> list = mapperObj.queryListBySql(sql);
            res.setData(list);
        } catch (Exception e) {
            throw new ServerInternalErrorException(ServiceCode.FAILED, e);
        }
        return res;
    }


    private boolean hasDelFlag(Class domainCla) {
        boolean hasDelFlag = false;
        try {
            hasDelFlag = domainCla.getMethod("getDelFlag") != null;
        } catch (Exception ee) {
            log.info("no delFlag field");
        }

        return hasDelFlag;

    }

    /**
     * 设置查询条件
     * ("columnName is null");
     * ("columnName is not null");
     * ("columnName =", value, "columnName");
     * ("columnName <>", value, "columnName");
     * ("columnName >", value, "columnName");
     * ("columnName >=", value, "columnName");
     * ("columnName <", value, "columnName");
     * ("columnName <=", value, "columnName");
     * ("columnName like", value, "columnName");
     * ("columnName not like", value, "columnName");
     * ("columnName in", values, "columnName");
     * ("columnName not in", values, "columnName");
     * ("columnName between", value1, value2, "columnName");
     * ("columnName not between", value1, value2, "columnName");
     *
     * @param criteriaClass 查询类
     * @param criteriaObj   查询对象
     * @param params
     */
    private void setCriteriaCondition(Class criteriaClass, Object criteriaObj, String entity, Map<String, Object> params) {

        try {
            Method createCriteria = criteriaClass.getMethod("createCriteria");
            Object o = createCriteria.invoke(criteriaObj);
            Class<?> aClass = o.getClass();

            JSONObject select = CURDConfig.selectCondition(entity);

            // 获取domain
            Class domainCla = getClassByClaPath(StringUtils.capitalize(entity));


            // 添加 delFlag = "ok" 条件
            if (hasDelFlag(domainCla)) {
                Method delFlagEqualTo = aClass.getMethod("andDelFlagEqualTo", String.class);
                delFlagEqualTo.invoke(o, "ok");
            }

            if (null == select) return;

            Set<String> keys = params.keySet();

            List<String> nameList = new ArrayList<>();

            for (String columnName : select.keySet()) {
                String name = CURDConfig.underlineToCamel(columnName, false);
                if (keys.contains(name)) {
                    nameList.add(columnName);
                }
            }

            for (String columnName : nameList) {
                String key = CURDConfig.underlineToCamel(columnName, false);
                String name = CURDConfig.underlineToCamel(columnName, true);
                String str = (String) select.get(columnName);
                String methodName = "and" + name + str;

                String par = params.get(key).toString();

                // 获取参数类型
                Class classByClaPath = getClassByClaPath(String.format("gen.Super%s", CURDConfig.underlineToCamel(entity, true)));
                Field field = classByClaPath.getDeclaredField(key);
                Class<?> type = field.getType();
                String className = type.getSimpleName();

                if (par.contains("[")) {
                    // 数组操作
                    JSONArray jsonArray = JSONArray.parseArray(par);

                    Object start = jsonArray.get(0).toString();
                    Object end = jsonArray.get(1).toString();

                    Method method = aClass.getMethod(methodName, type, type);
                    method.invoke(o, getClassValue(className, start.toString()), getClassValue(className, end.toString()));
                } else {
                    if (str.contains("Like")) {
                        par = "%" + par + "%";
                    }
                    Method method = aClass.getMethod(methodName, type);
                    method.invoke(o, getClassValue(className, par.toString()));
                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 转换类型
    private Object getClassValue(String className, String value) throws ParseException {
        log.info(className);
        if ("String".equals(className)) {
            return value;
        } else if ("Integer".equals(className)) {
            return new Integer(value);
        } else if ("Long".equals(className)) {
            return new Long(value);
        } else if ("Date".equals(className)) {

            SimpleDateFormat simpleDateFormat;
            if (value.length() > 15) {
                simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            } else {
                simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            }

            return simpleDateFormat.parse(value);
        }
        return "";
    }
}